#include "pyvim.h"

PyDoc_STRVAR(buffer_doc,
"A buffer model for a vim window\n");

static PyObject *
buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	return type->tp_alloc(type, 0);
}

static int
buffer_init(PvBufferObject *self, PyObject *args, PyObject *kwds)
{
	PyObject *m, *r;

	self->lines = PyList_New(0);
	if (self->lines == NULL)
		return -1;
	m = PyImport_ImportModule("_locale");
	if (m == NULL)
		return -1;
	r = PyObject_CallMethod(m, "_getdefaultlocale", NULL);
	if (r == NULL)
		return -1;
	self->encoding = PyTuple_GetItem(r, 1);
	Py_INCREF(self->encoding);
	if (self->encoding == NULL)
		return -1;
	self->linebr = PyUnicode_InternFromString("\n");
	Py_DECREF(m);
	Py_DECREF(r);

	return 0;
}

static void
buffer_dealloc(PyObject *me)
{
	PvBufferObject *self = (PvBufferObject *)me;
	Py_XDECREF(self->lines);
	Py_XDECREF(self->ffname);
	Py_XDECREF(self->bname);
	Py_XDECREF(self->name);
	Py_XDECREF(self->encoding);
	Py_XDECREF(self->linebr);
}

PyDoc_STRVAR(buffer_read_doc,
"A buffer model for a vim window\n");

static PyObject *
_setname(PvBufferObject *self, PyObject *name)
{
	PyObject *m, *ffname, *parts, *bname; 

	m = PyImport_ImportModule("nt");
	if (m == NULL)
		return NULL;

	ffname = PyObject_CallMethod(m, "_getfullpathname", "O", name);
	Py_DECREF(m);
	if (ffname == NULL) {
		return NULL;
	}
	parts = PyUnicode_Split(ffname, 
			PyUnicode_InternFromString("\\"), 10000);
	if (ffname == NULL) {
		Py_DECREF(ffname);
		return NULL;
	}

	bname = PyList_GetItem(parts, PyList_Size(parts) - 1);
	if (bname == NULL) {
		Py_DECREF(ffname);
		return NULL;
	}
	Py_INCREF(bname);
	Py_DECREF(parts);

	Py_XDECREF(self->ffname);
	Py_XDECREF(self->bname);
	Py_XDECREF(self->name);
	self->ffname = ffname;
	self->bname = bname;
	Py_INCREF(bname);
	self->name = bname;

	Py_RETURN_NONE;
}

static PyObject *
myread(PvBufferObject *buf, PyObject *name)
{
	PyObject *file = NULL;
	PyObject *io = NULL;
	PyObject *line = NULL;
	PyObject *line1 = NULL;
	int		length = 0;

	io = PyImport_ImportModule("_io");
	if (io == NULL)
		goto error;


	file = PyObject_CallMethod(io, "open", "OsiO", 
			name, "r", -1, buf->encoding);
	Py_XDECREF(io);
	if (file == NULL)
		goto error;

	if (buf->lines != NULL)
		Py_XDECREF(buf->lines);
	buf->lines = PyList_New(0);
	if (buf->lines == NULL) 
		goto error;
	
	while (1) {
		line = PyFile_GetLine(file, 0);
		if (line == NULL) 
			goto error;

		length = PyUnicode_GET_SIZE(line);
		
		if (length == 0) {
			Py_DECREF(line);
			break;
		}

		line1 = PyUnicode_ADDLINEBREAK(line, buf->linebr);
		PyList_Append(buf->lines, line1);
		Py_DECREF(line);
		if (line != line)
			Py_DECREF(line1);
	}

	Py_DECREF(file);
	if (_setname(buf, name) == NULL)
		goto error;

	Py_RETURN_NONE;

error:
	Py_XDECREF(file);
	Py_XDECREF(line);
	return NULL;
}

static PyObject *
buffer_read(PvBufferObject *self, PyObject *args)
{
	PyObject *name = NULL;

	if (!PyArg_ParseTuple(args, "O|O", &name, &(self->encoding)))
		return NULL;

	if (!myread(self, name))
		return NULL;
	Py_RETURN_NONE;
}

PyObject *
PvBuffer_Read(PyObject *self, wchar_t *name, wchar_t *encoding)
{
	PyObject *na = PyUnicode_FromWideChar(name, -1);
	if (na == NULL)
		return NULL;
	BUF_ENCODING(self) = PyUnicode_FromWideChar(encoding, -1);

	if (!myread((PvBufferObject *)self, na))
		return NULL;

	Py_XDECREF(na);

	Py_RETURN_NONE;
}

PyObject *
PvBuffer_Insert(PyObject *self, Py_ssize_t lnum, Py_ssize_t off, 
		PyObject *text)
{
	Py_ssize_t buf_len, text_linenr;
	int i;
	PyUnicodeObject *line = NULL;
	PyObject *nl = NULL;
	PyObject *lines = NULL;
	PyObject *first = NULL;
	PyObject *last = NULL;
	PyObject *tmpline = NULL;

	if (!PyUnicode_Check(text)) {
		PyErr_SetString(PyExc_TypeError, "text must be a unicode string");
		return NULL;
	}

	lines = PyUnicode_Splitlines(text, 1);
	if (lines == NULL)
		return NULL;
	text_linenr = PyList_Size(lines);

	if (BUF_LINES(self) == NULL) {
		BUF_LINES(self) = PyList_New(0);
		if (BUF_LINES(self) == NULL)
			goto error;
	}

	buf_len = PyList_Size(BUF_LINES(self));

	if (lnum > buf_len) {
		PyErr_Format(PyExc_ValueError,
			   "arg lnum:%u is too large, max is %d\n", lnum, buf_len);
		goto error;
	}

	if (text_linenr < 1)
		goto error;

	if (lnum == buf_len) {
		/* append lines */
		/* this line must remain ref if it end with line break,
		 * or must discard at the end if not.
		 */
		last = PyList_GetItem(lines, text_linenr-1);
		if (last == NULL)
			goto error;
		first = last;
		Py_INCREF(first);
		last = PyUnicode_ADDLINEBREAK(last, BUF_LINEBR(self));
		if (PyList_SetItem(lines, text_linenr-1, last) == -1)
			goto error;
		for (i = 0; i < text_linenr; i++) {
			PyList_Append(BUF_LINES(self), PyList_GetItem(lines, i));
		}
		if (last != first)
			Py_DECREF(first);
	} else {
		/* insert lines */
		Py_ssize_t	len;
		line = (PyUnicodeObject *)PyList_GetItem(BUF_LINES(self), lnum);
		if (line == NULL)
			goto error;
		len = PyUnicode_GET_SIZE(line);

		if (off >= len) {
			PyErr_SetString(PyExc_ValueError, 
					"offset value is larger than line length");
			goto error;
		} else {
			tmpline = PyList_GetItem(lines, 0);
			/* first line not end with line break, means only one line
			 * to insert
			 */
			if (PyUnicode_ENDSWITH(tmpline, BUF_LINEBR(self)) == 0) {
				nl = PyUnicode_Insert((PyObject *)line, off, tmpline);
				PyList_SetItem(BUF_LINES(self), lnum, nl);
			} else {
				if (text_linenr == 1) {
					first = PyUnicode_FromUnicode(line->str, off);
					last = PyUnicode_FromUnicode(line->str+off, len-off);
					tmpline = PyUnicode_Concat(first, tmpline);
					Py_DECREF(first);
					PyList_SetItem(BUF_LINES(self), lnum, tmpline);
					PyList_Insert(BUF_LINES(self), lnum+1, last);
					Py_DECREF(last);
				} else {
					first = PyUnicode_FromUnicode(line->str, off);
					last = PyUnicode_FromUnicode(line->str+off, len-off);
					tmpline = PyUnicode_Concat(first, tmpline);
					Py_DECREF(first);
					PyList_SetItem(BUF_LINES(self), lnum, tmpline);
					for (i = 1; i < text_linenr - 1; i++) {
						tmpline = PyList_GetItem(lines, i);
						PyList_Insert(BUF_LINES(self), i+lnum, tmpline);
					}
					tmpline = PyList_GetItem(lines, text_linenr-1);
					if (PyUnicode_ENDSWITH(tmpline, BUF_LINEBR(self))) {
						PyList_Insert(BUF_LINES(self), i+lnum, last);
						PyList_Insert(BUF_LINES(self), i+lnum, tmpline);
					} else {
						tmpline = PyUnicode_Concat(tmpline, last);
						PyList_Insert(BUF_LINES(self), i+lnum, tmpline);
					}
					Py_DECREF(last);
				}
			}
		}
	}

	Py_DECREF(lines);
	Py_RETURN_NONE;

error:
	Py_XDECREF(first);
	Py_XDECREF(last);
	Py_XDECREF(lines);
	Py_XDECREF(nl);
	return NULL;
}

PyDoc_STRVAR(buffer_insert_doc,
"insert(lnum, off):\n"
"Insert a string in the buf,\n"
"lnum: line number of the insert place.\n"
"off: offset at the line where inserted. \n"
);

static PyObject *
buffer_insert(PyObject *self, PyObject *args)
{
	Py_ssize_t lnum, off;
	PyObject *text;

	if (!PyArg_ParseTuple(args, "iiO", &lnum, &off, &text))
		return NULL;

	if (PvBuffer_Insert(self, lnum, off, text) == NULL)
		return NULL;

	Py_RETURN_NONE;
}

/* remove specified[start, end] character in line lnum */
PyObject *
PvBuffer_Remove(PyObject *self, Py_ssize_t lnum, Py_ssize_t start,
		Py_ssize_t end)
{
	PyUnicodeObject *line;
	PyObject *p1, *p2;
	int len, buf_len;

	buf_len = PyList_Size(BUF_LINES(self));
	if (lnum >= buf_len)
		Py_RETURN_NONE;

	line = (PyUnicodeObject *)PyList_GetItem(BUF_LINES(self), lnum);
	len = PyUnicode_GET_SIZE(line);
	p1 = PyUnicode_FromUnicode(line->str, start);

	printf("%d, %d, %d\n", lnum, start, end);
	if (end >= len - 1 || end == -1) {
		printf("last line \n");
		/* delete start to line end and join next line */
		end = len - 1;
		if (lnum >= buf_len - 1) {
			/* lnum is the last line */
			if (start == 0 && lnum > 0) {
				/* delete the whole last line */
				PySequence_DelItem(BUF_LINES(self), lnum);
				goto end;
			}
			else 
				line = (PyUnicodeObject *)PyUnicode_Concat(p1, 
						PyUnicode_InternFromString("\n"));
		} else {
			p2 = PyList_GetItem(BUF_LINES(self), lnum + 1);
			Py_INCREF(p2);
			PySequence_DelItem(BUF_LINES(self), lnum + 1);
			line = (PyUnicodeObject *)PyUnicode_Concat(p1, p2);
		}
	} else {
		printf("normal line \n");
		p2 = PyUnicode_FromUnicode(line->str + end + 1, len - end - 1);
		line = (PyUnicodeObject *)PyUnicode_Concat(p1, p2);
		Py_DECREF(p2);
	}
	PyList_SetItem(BUF_LINES(self), lnum, (PyObject *)line);

end:
	Py_XDECREF(p1);
	Py_RETURN_NONE;
}

PyDoc_STRVAR(buffer_remove_doc,
"remove(lnum, off1, off2) \n"
"remove characters from buffer\n"
"lnum: line number of the remove place.\n"
"start: start pos to remove \n"
"end: end pos to remove.\n"
);

static PyObject *
buffer_remove(PyObject *self, PyObject *args)
{
	Py_ssize_t lnum, start, end;

	if (!PyArg_ParseTuple(args, "iii", &lnum, &start, &end))
		return NULL;

	return PvBuffer_Remove(self, lnum, start, end);
}

PyDoc_STRVAR(buffer_rmline_doc,
"rmline(lnum) \n"
"delete the specified line\n"
"lnum: line number.\n"
);

static PyObject *
buffer_rmline(PyObject *self, PyObject *args)
{
	Py_ssize_t lnum;

	if (!PyArg_ParseTuple(args, "i", &lnum))
		return NULL;

	return PvBuffer_DelLine(self, lnum);
}

/* borrow the reference */
PyObject *
PvBuffer_GetLine(PyObject *self, int lnum)
{
	PyObject *line;

	line = PyList_GetItem(BUF_LINES(self), lnum);

	return line;
}

static PyObject *
buffer_getlength(PvBufferObject *self, void *closure)
{
	if (self->lines == NULL)
		return PyLong_FromSsize_t(0);
	return PyLong_FromSsize_t(Py_SIZE(self->lines));
}

static int
buffer_setlength(PvBufferObject *self, PyObject *value, void *closure)
{
	PyErr_SetString(PyExc_TypeError, "length is readonly");
	return -1;
}

static PyGetSetDef buffer_getseters[] = {
	{"length", (getter)buffer_getlength, (setter)buffer_setlength, 
		"The total number of buffer lines"},
	{NULL}
};

static PyMemberDef buffer_members[] = {
	{"lines", T_OBJECT, offsetof(PvBufferObject, lines), 0, 
		"full path file name"},
	{"ffname", T_OBJECT, offsetof(PvBufferObject, ffname), 0, 
		"full path file name"},
	{"bname", T_OBJECT, offsetof(PvBufferObject, bname), 0, 
		"base name of file"},
	{"name", T_OBJECT, offsetof(PvBufferObject, name), 0, 
		"the name of the buffer"},
	{"encoding", T_OBJECT, offsetof(PvBufferObject, encoding), 0, 
		"the encoding the decode the file"},
	{NULL}
};

static PyMethodDef buffer_methods[] = {
	{"read", (PyCFunction)buffer_read, METH_VARARGS,
		buffer_read_doc},
	{"insert", (PyCFunction)buffer_insert, METH_VARARGS,
		buffer_insert_doc},
	{"remove", (PyCFunction)buffer_remove, METH_VARARGS,
		buffer_remove_doc},
	{"rmline", (PyCFunction)buffer_rmline, METH_VARARGS,
		buffer_rmline_doc},
	{NULL, NULL}
};


PyTypeObject PvBuffer_Type = {
	PyObject_HEAD_INIT(0)
        "pyvim.buffer",		/* tp_name */
        sizeof(PvBufferObject),		/* tp_basicsize */
        0,				/* tp_itemsize */
        buffer_dealloc,		/* tp_dealloc */
        0,				/* tp_print */
        0,				/* tp_getattr */
        0,				/* tp_setattr */
        0,				/* tp_reserved */
        0,				/* tp_repr */
        0,				/* tp_as_number */
        0,				/* tp_as_sequence */
        0,				/* tp_as_mapping */
        0,				/* tp_hash */
        0,				/* tp_call */
        0,				/* tp_str */
        PyObject_GenericGetAttr,	/* tp_getattro */
        0,				/* tp_setattro */
        0,				/* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,		/* tp_flags */
        buffer_doc,		/* tp_doc */
        0,				/* tp_traverse */
        0,				/* tp_clear */
        0,				/* tp_richcompare */
        0,				/* tp_weaklistoffset */
        0,				/* tp_iter */
        0,				/* tp_iternext */
        buffer_methods,		/* tp_methods */
        buffer_members,				/* tp_members */
        buffer_getseters,				/* tp_getset */
        0,				/* tp_base */
        0,				/* tp_dict */
        0,				/* tp_descr_get */
        0,				/* tp_descr_set */
        0,				/* tp_dictoffset */
        (initproc)buffer_init,				/* tp_init */
        0,				/* tp_alloc */
        buffer_new,				/* tp_new */
        0,				/* tp_free */
        0,				/* tp_is_gc */
};

/*
Local variables:
c-basic-offset: 4
indent-tabs-mode: nil
End:
*/
